package com.lwl.mini.builder;

import com.lwl.mini.executor.Resources;
import com.lwl.mini.mapping.Environment;
import com.lwl.mini.parsing.XNode;
import com.lwl.mini.parsing.XPathParser;
import com.lwl.mini.session.Configuration;
import com.lwl.mini.util.ObjectUtils;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.namespace.QName;
import java.io.Reader;
import java.util.*;

/**
 * 解析XML文件
 *
 * @author lwl
 * @create 2022/4/9 12:38
 */
public class XMLConfigBuilder {
    /**
     * 防止重复解析配置
     */
    private boolean parsed;
    private XPathParser parser;
    private Configuration configuration;

    public XMLConfigBuilder(Reader reader) {
        this.parser = new XPathParser(new InputSource(reader));
        this.parsed = false;
    }

    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("每个XMLConfigBuilder只能使用一次,请勿重复加载!");
        }
        parsed = true;
        configuration = Configuration.getInstance();
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    /**
     * 将document文档对象中的标签转换为Configuration对象对应的属性
     *
     * @param root
     */
    private void parseConfiguration(XNode root) {
        try {
            propertiesElement(root.evalNode("properties"));
            environmentElement(root.evalNode("environment"));
            settingsElement(root.evalNode("settings"));
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration.Cause:" + e, e);
        }
    }


    /**
     * 解析environment标签
     */
    private void environmentElement(XNode context) {
        Properties properties = configuration.getProperties();
        Environment environment = new Environment();
        if (context == null && properties != null) {
            //未使用environment标签加载配置类，使用了properties标签加载
            for (Object obj : properties.keySet()) {
                environment.addAttributes(properties, (String) obj);
            }
        } else if (context != null && properties == null) {
            //使用了environment标签加载配置类，未使用properties标签
            //获取的是environment下的datasource标签
            for (Node cur : getChildrenList(context.getChildren())) {
                //获取dataSource下的子标签
                for (Node child : getChildrenList(cur.getChildNodes())) {
                    Properties prop1 = parseNamedNodeMap(child);
                    for (Object obj : prop1.keySet()) {
                        environment.addAttributes(prop1, (String) obj);
                    }
                }
            }
        } else {
            throw new BuilderException("指定的数据源异常,请检查配置文件中environment标签或properties标签,两种标签不能共存");
        }
        configuration.setEnvironment(environment);
    }

    /**
     * 解析properties标签
     * 当properties标签既使用了resource属性，又使用了property标签，将优先加载resource指定的路径
     */
    public void propertiesElement(XNode context) {
        if (context != null) {
            NamedNodeMap attributes = context.getAttributes();
            String name = null;
            String value = null;
            if (attributes.getLength() > 0) {
                //resource属性存在时
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node item = attributes.item(i);
                    name = item.getNodeName();
                    value = item.getNodeValue();
                }
                if ("resource".equals(name)) {
                    Properties prop = Resources.getResourceAsProperties(value);
                    configuration.setProperties(prop);
                }
            } else {
                //property标签存在时
                Properties properties = new Properties();
                for (Node child : getChildrenList(context.getChildren())) {
                    Properties prop1 = parseNamedNodeMap(child);
                    for (Object obj : prop1.keySet()) {
                        properties.put(obj, prop1.get(obj));
                    }
                }
                configuration.setProperties(properties);
            }
        }
    }

    /**
     * @return 子节点Node的集合
     */
    public List<Node> getChildrenList(NodeList nodeList) {
        List<Node> children = new ArrayList<>();
        if (nodeList != null) {
            for (int i = 0, n = nodeList.getLength(); i < n; i++) {
                Node item = nodeList.item(i);
                if (item.getNodeType() == Node.ELEMENT_NODE) {
                    children.add(item);
                }
            }
        }
        return children;
    }

    /**
     * 用于Property和setting标签
     * 转换NamedNodeMap结构为Properties
     *
     * @param node
     * @return
     */
    public Properties parseNamedNodeMap(Node node) {
        Properties properties = new Properties();
        NamedNodeMap attributes = node.getAttributes();
        String key = null;
        String value = null;
        for (int i = 0; i < attributes.getLength(); i++) {
            Node cur = attributes.item(i);
            if ((i & 1) == 0) {
                key = cur.getNodeValue();
            } else {
                value = cur.getNodeValue();
            }
        }
        properties.put(key, value);
        return properties;
    }

    /**
     * 解析settings标签
     */
    public void settingsElement(XNode content) {
        if (content != null) {
            Map<String, Boolean> settings = configuration.getSettings();
            for (Node cur : getChildrenList(content.getChildren())) {
                Properties prop = parseNamedNodeMap(cur);
                for (Object str : prop.keySet()) {
                    if (settings.containsKey(str)) {
                        settings.put((String) str, Boolean.valueOf(prop.getProperty((String) str)));
                    }
                }
            }
        }
    }

    /**
     * 解析mappers标签
     */
    private void mapperElement(XNode context) {
        if (context != null) {
            Map<String, String> mappers = configuration.getMappers();
            for (Node cur : getChildrenList(context.getChildren())) {
                String key = cur.getNodeName();
                String value = null;
                NamedNodeMap attributes = cur.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node item = attributes.item(i);
                    value = item.getNodeValue();
                }
                if (mappers.containsKey(key)) {
                    mappers.put(key, value);
                }
            }
            configuration.addMappers(mappers.get("package"));
        }
    }
}
